Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-03-15 16:02:53 -07:00
commit d202bd1d1d
16 changed files with 121 additions and 31 deletions

View file

@ -9,5 +9,5 @@ class Image(ActivityObject):
url: str
name: str = ""
type: str = "Image"
id: str = ""
type: str = "Document"
id: str = None

View file

@ -445,7 +445,11 @@ def unfurl_related_field(related_field, sort_field=None):
unfurl_related_field(i) for i in related_field.order_by(sort_field).all()
]
if related_field.reverse_unfurl:
return related_field.field_to_activity()
# if it's a one-to-one (key pair)
if hasattr(related_field, "field_to_activity"):
return related_field.field_to_activity()
# if it's one-to-many (attachments)
return related_field.to_activity()
return related_field.remote_id

View file

@ -25,7 +25,11 @@ class Image(Attachment):
""" an image attachment """
image = fields.ImageField(
upload_to="status/", null=True, blank=True, activitypub_field="url"
upload_to="status/",
null=True,
blank=True,
activitypub_field="url",
alt_field="caption",
)
caption = fields.TextField(null=True, blank=True, activitypub_field="name")

View file

@ -1,7 +1,7 @@
""" database schema for books and shelves """
import re
from django.db import models
from django.db import models, transaction
from model_utils.managers import InheritanceManager
from bookwyrm import activitypub
@ -148,6 +148,15 @@ class Work(OrderedCollectionPageMixin, Book):
""" in case the default edition is not set """
return self.default_edition or self.editions.order_by("-edition_rank").first()
@transaction.atomic()
def reset_default_edition(self):
""" sets a new default edition based on computed rank """
self.default_edition = None
# editions are re-ranked implicitly
self.save()
self.default_edition = self.get_default_edition()
self.save()
def to_edition_list(self, **kwargs):
""" an ordered collection of editions """
return self.to_ordered_collection(
@ -200,9 +209,13 @@ class Edition(Book):
activity_serializer = activitypub.Edition
name_field = "title"
def get_rank(self):
def get_rank(self, ignore_default=False):
""" calculate how complete the data is on this edition """
if self.parent_work and self.parent_work.default_edition == self:
if (
not ignore_default
and self.parent_work
and self.parent_work.default_edition == self
):
# default edition has the highest rank
return 20
rank = 0

View file

@ -5,6 +5,7 @@ import re
from django.apps import apps
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.template.loader import get_template
from django.utils import timezone
from model_utils.managers import InheritanceManager
@ -309,7 +310,8 @@ class ReviewRating(Review):
@property
def pure_content(self):
return 'Rated "{}": {:d} stars'.format(self.book.title, self.rating)
template = get_template("snippets/generated_status/rating.html")
return template.render({"book": self.book, "rating": self.rating}).strip()
activity_serializer = activitypub.Rating
pure_type = "Note"

View file

@ -233,7 +233,7 @@
</section>
{% endif %}
{% if lists.exists %}
{% if lists.exists or request.user.list_set.exists %}
<section class="content block">
<h2 class="title is-5">{% trans "Lists" %}</h2>
<ul>
@ -241,6 +241,26 @@
<li><a href="{{ list.local_path }}">{{ list.name }}</a></li>
{% endfor %}
</ul>
{% if request.user.list_set.exists %}
<form name="list-add" method="post" action="{% url 'list-add-book' %}">
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<label class="label" for="id_list">{% trans "Add to list" %}</label>
<div class="field has-addons">
<div class="select control">
<select name="list" id="id_list">
{% for list in user.list_set.all %}
<option value="{{ list.id }}">{{ list.name }}</option>
{% endfor %}
</select>
</div>
<div class="control">
<button type="submit" class="button is-link">{% trans "Add" %}</button>
</div>
</div>
</form>
{% endif %}
</section>
{% endif %}
</div>

View file

@ -6,7 +6,7 @@
{% block content %}
<div class="block">
<h1 class="title">{% blocktrans with path=work.local_path work_title=work.title %}Editions of <a href="{{ work_path }}">"{{ work_title }}"</a>{% endblocktrans %}</h1>
<h1 class="title">{% blocktrans with work_path=work.local_path work_title=work.title %}Editions of <a href="{{ work_path }}">"{{ work_title }}"</a>{% endblocktrans %}</h1>
{% include 'snippets/book_tiles.html' with books=editions %}
</div>

View file

@ -83,9 +83,10 @@
</div>
<div class="column">
<p>{% include 'snippets/book_titleby.html' with book=book %}</p>
<form name="add-book" method="post" action="{% url 'list-add-book' list.id %}">
<form name="add-book" method="post" action="{% url 'list-add-book' %}">
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="list" value="{{ list.id }}">
<button type="submit" class="button is-small is-link">{% if list.curation == 'open' or request.user == list.user %}{% trans "Add" %}{% else %}{% trans "Suggest" %}{% endif %}</button>
</form>
</div>

View file

@ -0,0 +1,3 @@
{% load i18n %}{% load humanize %}
{% blocktrans with title=book.title path=book.remote_id rating=rating count counter=rating %}Rated <em><a href="{{ path }}">{{ title }}</a></em>: {{ rating }} star{% plural %}Rated <em><a href="{{ path }}">{{ title }}</a></em>: {{ rating }} stars{% endblocktrans %}

View file

@ -123,7 +123,7 @@ class BaseActivity(TestCase):
summary="",
publicKey={"id": "hi", "owner": self.user.remote_id, "publicKeyPem": "hi"},
endpoints={},
icon={"type": "Image", "url": "http://www.example.com/image.jpg"},
icon={"type": "Document", "url": "http://www.example.com/image.jpg"},
)
responses.add(
@ -194,7 +194,7 @@ class BaseActivity(TestCase):
{
"url": "http://www.example.com/image.jpg",
"name": "alt text",
"type": "Image",
"type": "Document",
}
],
)
@ -224,7 +224,7 @@ class BaseActivity(TestCase):
data = {
"url": "http://www.example.com/image.jpg",
"name": "alt text",
"type": "Image",
"type": "Document",
}
responses.add(
responses.GET,

View file

@ -404,7 +404,7 @@ class ActivitypubFields(TestCase):
)
)
self.assertEqual(output.name, "alt text")
self.assertEqual(output.type, "Image")
self.assertEqual(output.type, "Document")
instance = fields.ImageField()

View file

@ -169,7 +169,7 @@ class Status(TestCase):
self.assertEqual(activity["type"], "Note")
self.assertEqual(activity["sensitive"], False)
self.assertIsInstance(activity["attachment"], list)
self.assertEqual(activity["attachment"][0].type, "Image")
self.assertEqual(activity["attachment"][0].type, "Document")
self.assertEqual(
activity["attachment"][0].url,
"https://%s%s" % (settings.DOMAIN, self.book.cover.url),
@ -200,7 +200,7 @@ class Status(TestCase):
'test content<p>(comment on <a href="%s">"Test Edition"</a>)</p>'
% self.book.remote_id,
)
self.assertEqual(activity["attachment"][0].type, "Image")
self.assertEqual(activity["attachment"][0].type, "Document")
self.assertEqual(
activity["attachment"][0].url,
"https://%s%s" % (settings.DOMAIN, self.book.cover.url),
@ -238,7 +238,7 @@ class Status(TestCase):
'a sickening sense <p>-- <a href="%s">"Test Edition"</a></p>'
"test content" % self.book.remote_id,
)
self.assertEqual(activity["attachment"][0].type, "Image")
self.assertEqual(activity["attachment"][0].type, "Document")
self.assertEqual(
activity["attachment"][0].url,
"https://%s%s" % (settings.DOMAIN, self.book.cover.url),
@ -278,7 +278,7 @@ class Status(TestCase):
activity["name"], 'Review of "%s" (3 stars): Review name' % self.book.title
)
self.assertEqual(activity["content"], "test content")
self.assertEqual(activity["attachment"][0].type, "Image")
self.assertEqual(activity["attachment"][0].type, "Document")
self.assertEqual(
activity["attachment"][0].url,
"https://%s%s" % (settings.DOMAIN, self.book.cover.url),

View file

@ -1,5 +1,10 @@
""" test for app action functionality """
from io import BytesIO
from unittest.mock import patch
import pathlib
from PIL import Image
from django.core.files.base import ContentFile
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
@ -9,8 +14,8 @@ from bookwyrm import views
from bookwyrm.activitypub import ActivitypubResponse
class FeedMessageViews(TestCase):
""" dms """
class FeedViews(TestCase):
""" activity feed, statuses, dms """
def setUp(self):
""" we need basic test data and mocks """
@ -59,6 +64,42 @@ class FeedMessageViews(TestCase):
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_status_page_with_image(self):
""" there are so many views, this just makes sure it LOADS """
view = views.Status.as_view()
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg"
)
image = Image.open(image_file)
output = BytesIO()
image.save(output, format=image.format)
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
status = models.Review.objects.create(
content="hi",
user=self.local_user,
book=self.book,
)
attachment = models.Image.objects.create(
status=status, caption="alt text here"
)
attachment.image.save("test.jpg", ContentFile(output.getvalue()))
request = self.factory.get("")
request.user = self.local_user
with patch("bookwyrm.views.feed.is_api_request") as is_api:
is_api.return_value = False
result = view(request, "mouse", status.id)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)
with patch("bookwyrm.views.feed.is_api_request") as is_api:
is_api.return_value = True
result = view(request, "mouse", status.id)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_replies_page(self):
""" there are so many views, this just makes sure it LOADS """
view = views.Replies.as_view()

View file

@ -271,11 +271,12 @@ class ListViews(TestCase):
"",
{
"book": self.book.id,
"list": self.list.id,
},
)
request.user = self.local_user
views.list.add_book(request, self.list.id)
views.list.add_book(request)
item = self.list.listitem_set.get()
self.assertEqual(item.book, self.book)
self.assertEqual(item.user, self.local_user)
@ -300,11 +301,12 @@ class ListViews(TestCase):
"",
{
"book": self.book.id,
"list": self.list.id,
},
)
request.user = self.rat
views.list.add_book(request, self.list.id)
views.list.add_book(request)
item = self.list.listitem_set.get()
self.assertEqual(item.book, self.book)
self.assertEqual(item.user, self.rat)
@ -330,11 +332,12 @@ class ListViews(TestCase):
"",
{
"book": self.book.id,
"list": self.list.id,
},
)
request.user = self.rat
views.list.add_book(request, self.list.id)
views.list.add_book(request)
item = self.list.listitem_set.get()
self.assertEqual(item.book, self.book)
self.assertEqual(item.user, self.rat)
@ -360,11 +363,12 @@ class ListViews(TestCase):
"",
{
"book": self.book.id,
"list": self.list.id,
},
)
request.user = self.local_user
views.list.add_book(request, self.list.id)
views.list.add_book(request)
item = self.list.listitem_set.get()
self.assertEqual(item.book, self.book)
self.assertEqual(item.user, self.local_user)

View file

@ -117,9 +117,7 @@ urlpatterns = [
# lists
re_path(r"^list/?$", views.Lists.as_view(), name="lists"),
re_path(r"^list/(?P<list_id>\d+)(.json)?/?$", views.List.as_view(), name="list"),
re_path(
r"^list/(?P<list_id>\d+)/add/?$", views.list.add_book, name="list-add-book"
),
re_path(r"^list/add-book/?$", views.list.add_book, name="list-add-book"),
re_path(
r"^list/(?P<list_id>\d+)/remove/?$",
views.list.remove_book,

View file

@ -173,9 +173,9 @@ class Curate(View):
@require_POST
def add_book(request, list_id):
def add_book(request):
""" put a book on a list """
book_list = get_object_or_404(models.List, id=list_id)
book_list = get_object_or_404(models.List, id=request.POST.get("list"))
if not object_visible_to_user(request.user, book_list):
return HttpResponseNotFound()
@ -204,7 +204,7 @@ def add_book(request, list_id):
# if the book is already on the list, don't flip out
pass
return redirect("list", list_id)
return redirect("list", book_list.id)
@require_POST