mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-15 12:46:38 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
fd3c6d1d21
14 changed files with 131 additions and 49 deletions
|
@ -69,7 +69,13 @@ class Undo(Verb):
|
|||
model = None
|
||||
if self.object.type == "Follow":
|
||||
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 we don't have the object, we can't undo it. happens a lot with boosts
|
||||
return
|
||||
|
|
|
@ -262,9 +262,10 @@ class SearchResult:
|
|||
|
||||
title: str
|
||||
key: str
|
||||
author: str
|
||||
year: str
|
||||
connector: object
|
||||
author: str = None
|
||||
year: str = None
|
||||
cover: str = None
|
||||
confidence: int = 1
|
||||
|
||||
def __repr__(self):
|
||||
|
|
|
@ -24,5 +24,4 @@ class Connector(AbstractMinimalConnector):
|
|||
return data
|
||||
|
||||
def format_isbn_search_result(self, search_result):
|
||||
search_result["connector"] = self
|
||||
return SearchResult(**search_result)
|
||||
return self.format_search_result(search_result)
|
||||
|
|
|
@ -95,10 +95,12 @@ class Connector(AbstractConnector):
|
|||
url = "%s%s" % (self.base_url, author_id)
|
||||
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 """
|
||||
if not cover_blob:
|
||||
return None
|
||||
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)
|
||||
|
||||
def parse_search_data(self, data):
|
||||
|
@ -108,12 +110,15 @@ class Connector(AbstractConnector):
|
|||
# build the remote id from the openlibrary key
|
||||
key = self.books_url + search_result["key"]
|
||||
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(
|
||||
title=search_result.get("title"),
|
||||
key=key,
|
||||
author=", ".join(author),
|
||||
connector=self,
|
||||
year=search_result.get("first_publish_year"),
|
||||
cover=cover,
|
||||
)
|
||||
|
||||
def parse_isbn_search_data(self, data):
|
||||
|
|
|
@ -67,20 +67,12 @@ class Connector(AbstractConnector):
|
|||
if search_result.published_date
|
||||
else None,
|
||||
connector=self,
|
||||
cover="%s%s" % (self.covers_url, search_result.cover),
|
||||
confidence=search_result.rank if hasattr(search_result, "rank") else 1,
|
||||
)
|
||||
|
||||
def format_isbn_search_result(self, search_result):
|
||||
return SearchResult(
|
||||
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,
|
||||
)
|
||||
return self.format_search_result(search_result)
|
||||
|
||||
def is_work_data(self, data):
|
||||
pass
|
||||
|
|
|
@ -76,7 +76,7 @@ def init_connectors():
|
|||
connector_file="self_connector",
|
||||
base_url="https://%s" % 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,
|
||||
isbn_search_url="https://%s/isbn/" % DOMAIN,
|
||||
priority=1,
|
||||
|
@ -88,7 +88,7 @@ def init_connectors():
|
|||
connector_file="bookwyrm_connector",
|
||||
base_url="https://bookwyrm.social",
|
||||
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=",
|
||||
isbn_search_url="https://bookwyrm.social/isbn/",
|
||||
priority=2,
|
||||
|
|
|
@ -78,13 +78,22 @@
|
|||
</dl>
|
||||
|
||||
<p>
|
||||
{% if book.physical_format and not book.pages %}
|
||||
{{ book.physical_format | title }}
|
||||
{% elif book.physical_format and book.pages %}
|
||||
{% blocktrans with format=book.physical_format|title pages=book.pages %}{{ format }}, {{ pages }} pages{% endblocktrans %}
|
||||
{% elif book.pages %}
|
||||
{% blocktrans with pages=book.pages %}{{ pages }} pages{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% if book.physical_format and not book.pages %}
|
||||
{{ book.physical_format | title }}
|
||||
{% elif book.physical_format and book.pages %}
|
||||
{% blocktrans with format=book.physical_format|title pages=book.pages %}{{ format }}, {{ pages }} pages{% endblocktrans %}
|
||||
{% elif book.pages %}
|
||||
{% blocktrans with pages=book.pages %}{{ pages }} pages{% endblocktrans %}
|
||||
{% 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>
|
||||
|
||||
{% if book.openlibrary_key %}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<ul>
|
||||
{% for result in local_results.results %}
|
||||
<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>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -50,12 +50,7 @@
|
|||
<ul>
|
||||
{% for result in result_set.results %}
|
||||
<li class="pb-4">
|
||||
<form action="/resolve-book" method="POST">
|
||||
{% 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>
|
||||
{% include 'snippets/search_result_text.html' with result=result remote_result=True %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
{% load i18n %}
|
||||
{% 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 %}
|
||||
{% include 'snippets/block_button.html' %}
|
||||
{% else %}
|
||||
|
||||
<div class="field has-addons">
|
||||
<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 %}
|
||||
<input type="hidden" name="user" value="{{ user.username }}">
|
||||
{% if user.manually_approves_followers %}
|
||||
|
@ -21,10 +15,14 @@
|
|||
<button class="button is-small is-link" type="submit">{% trans "Follow" %}</button>
|
||||
{% endif %}
|
||||
</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 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
<div class="control">
|
||||
|
|
|
@ -1,3 +1,34 @@
|
|||
{% load i18n %}
|
||||
<strong>{% if link %}<a href="{{ result.key }}">{{ result.title }}</a>{% else %}{{ result.title }}{% endif %}</strong>
|
||||
{% if result.author %} {% blocktrans with author=result.author %}by {{ author }}{% endblocktrans %}{% endif %}{% if result.year %} ({{ result.year }}){% endif %}
|
||||
<div class="columns is-mobile">
|
||||
<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>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
""" test for app action functionality """
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
@ -117,6 +119,8 @@ class BookViews(TestCase):
|
|||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.unfollow(request)
|
||||
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)
|
||||
|
||||
|
|
|
@ -275,6 +275,36 @@ class Inbox(TestCase):
|
|||
follow = models.UserFollows.objects.all()
|
||||
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):
|
||||
""" remove a relationship """
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
|
|
|
@ -188,8 +188,8 @@ urlpatterns = [
|
|||
re_path(r"^start-reading/(?P<book_id>\d+)/?$", views.start_reading),
|
||||
re_path(r"^finish-reading/(?P<book_id>\d+)/?$", views.finish_reading),
|
||||
# following
|
||||
re_path(r"^follow/?$", views.follow),
|
||||
re_path(r"^unfollow/?$", views.unfollow),
|
||||
re_path(r"^follow/?$", views.follow, name="follow"),
|
||||
re_path(r"^unfollow/?$", views.unfollow, name="unfollow"),
|
||||
re_path(r"^accept-follow-request/?$", views.accept_follow_request),
|
||||
re_path(r"^delete-follow-request/?$", views.delete_follow_request),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
|
|
@ -40,10 +40,22 @@ def unfollow(request):
|
|||
except models.User.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
models.UserFollows.objects.get(
|
||||
user_subject=request.user, user_object=to_unfollow
|
||||
).delete()
|
||||
return redirect(to_unfollow.local_path)
|
||||
try:
|
||||
models.UserFollows.objects.get(
|
||||
user_subject=request.user, user_object=to_unfollow
|
||||
).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
|
||||
|
|
Loading…
Reference in a new issue