mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-25 11:01:12 +00:00
Merge branch 'main' into export-fixes
This commit is contained in:
commit
2c59908ddd
15 changed files with 7420 additions and 13 deletions
46
bookwyrm/migrations/0195_alter_user_preferred_language.py
Normal file
46
bookwyrm/migrations/0195_alter_user_preferred_language.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Generated by Django 3.2.23 on 2024-02-21 00:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0194_merge_20240203_1619"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="preferred_language",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("en-us", "English"),
|
||||
("ca-es", "Català (Catalan)"),
|
||||
("de-de", "Deutsch (German)"),
|
||||
("eo-uy", "Esperanto (Esperanto)"),
|
||||
("es-es", "Español (Spanish)"),
|
||||
("eu-es", "Euskara (Basque)"),
|
||||
("gl-es", "Galego (Galician)"),
|
||||
("it-it", "Italiano (Italian)"),
|
||||
("ko-kr", "한국어 (Korean)"),
|
||||
("fi-fi", "Suomi (Finnish)"),
|
||||
("fr-fr", "Français (French)"),
|
||||
("lt-lt", "Lietuvių (Lithuanian)"),
|
||||
("nl-nl", "Nederlands (Dutch)"),
|
||||
("no-no", "Norsk (Norwegian)"),
|
||||
("pl-pl", "Polski (Polish)"),
|
||||
("pt-br", "Português do Brasil (Brazilian Portuguese)"),
|
||||
("pt-pt", "Português Europeu (European Portuguese)"),
|
||||
("ro-ro", "Română (Romanian)"),
|
||||
("sv-se", "Svenska (Swedish)"),
|
||||
("uk-ua", "Українська (Ukrainian)"),
|
||||
("zh-hans", "简体中文 (Simplified Chinese)"),
|
||||
("zh-hant", "繁體中文 (Traditional Chinese)"),
|
||||
],
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -321,6 +321,7 @@ LANGUAGES = [
|
|||
("eu-es", _("Euskara (Basque)")),
|
||||
("gl-es", _("Galego (Galician)")),
|
||||
("it-it", _("Italiano (Italian)")),
|
||||
("ko-kr", _("한국어 (Korean)")),
|
||||
("fi-fi", _("Suomi (Finnish)")),
|
||||
("fr-fr", _("Français (French)")),
|
||||
("lt-lt", _("Lietuvių (Lithuanian)")),
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% trans "Search for a book, user, or list" as search_placeholder %}
|
||||
{% trans "Search for a book, author, user, or list" as search_placeholder %}
|
||||
{% else %}
|
||||
{% trans "Search for a book" as search_placeholder %}
|
||||
{% endif %}
|
||||
|
|
17
bookwyrm/templates/search/author.html
Normal file
17
bookwyrm/templates/search/author.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% extends 'search/layout.html' %}
|
||||
|
||||
{% block panel %}
|
||||
|
||||
{% if results %}
|
||||
<ul class="block">
|
||||
{% for author in results %}
|
||||
<li class="">
|
||||
<a href="{{ author.local_path }}" class="author" itemprop="author" itemscope itemtype="https://schema.org/Thing">
|
||||
<span itemprop="name">{{ author.name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -20,6 +20,7 @@
|
|||
<div class="select" aria-label="{% trans 'Search type' %}">
|
||||
<select name="type">
|
||||
<option value="book" {% if type == "book" %}selected{% endif %}>{% trans "Books" %}</option>
|
||||
<option value="author" {% if type == "author" %}selected{% endif %}>{% trans "Authors" %}</option>
|
||||
{% if request.user.is_authenticated %}
|
||||
<option value="user" {% if type == "user" %}selected{% endif %}>{% trans "Users" %}</option>
|
||||
{% endif %}
|
||||
|
@ -42,6 +43,9 @@
|
|||
<li{% if type == "book" %} class="is-active"{% endif %}>
|
||||
<a href="{% url 'search' %}?q={{ query }}&type=book">{% trans "Books" %}</a>
|
||||
</li>
|
||||
<li{% if type == "author" %} class="is-active"{% endif %}>
|
||||
<a href="{% url 'search' %}?q={{ query }}&type=author">{% trans "Authors" %}</a>
|
||||
</li>
|
||||
{% if request.user.is_authenticated %}
|
||||
<li{% if type == "user" %} class="is-active"{% endif %}>
|
||||
<a href="{% url 'search' %}?q={{ query }}&type=user">{% trans "Users" %}</a>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{% block filter %}
|
||||
<div class="control">
|
||||
<label class="label" for="filter_query">{% trans 'Filter by keyword' %}</label>
|
||||
<input aria-label="Filter by keyword" id="my-books-filter" class="input" type="text" name="filter" placeholder="{% trans 'Enter text here' %}" value="{{ shelves_filter_query|default:'' }}" spellcheck="false" />
|
||||
<label class="label" for="my-books-filter">{% trans 'Filter by keyword' %}</label>
|
||||
<input id="my-books-filter" class="input" type="text" name="filter" placeholder="{% trans 'Enter text here' %}" value="{{ shelves_filter_query|default:'' }}" spellcheck="false" />
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -13,16 +13,26 @@ def validate_html(html):
|
|||
"warn-proprietary-attributes": False,
|
||||
},
|
||||
)
|
||||
# idk how else to filter out these unescape amp errs
|
||||
# Tidy's parser is strict when validating unescaped/encoded ampersands found within
|
||||
# the html document that are notpart of a character or entity reference
|
||||
# (eg: `&` or `&`). Despite the fact the HTML5 spec no longer recommends
|
||||
# escaping ampersands in URLs, Tidy will still complain if they are used as query
|
||||
# param keys. Unfortunately, there is no way currently to configure tidy to ignore
|
||||
# this so we must explictly redlist related strings that will appear in Tidy's
|
||||
# errors output.
|
||||
#
|
||||
# See further discussion: https://github.com/htacg/tidy-html5/issues/1017
|
||||
excluded = [
|
||||
"&book",
|
||||
"&type",
|
||||
"&resolved",
|
||||
"id and name attribute",
|
||||
"illegal characters found in URI",
|
||||
"escaping malformed URI reference",
|
||||
"&filter",
|
||||
]
|
||||
errors = "\n".join(
|
||||
e
|
||||
for e in errors.split("\n")
|
||||
if "&book" not in e
|
||||
and "&type" not in e
|
||||
and "&resolved" not in e
|
||||
and "id and name attribute" not in e
|
||||
and "illegal characters found in URI" not in e
|
||||
and "escaping malformed URI reference" not in e
|
||||
e for e in errors.split("\n") if not any(exclude in e for exclude in excluded)
|
||||
)
|
||||
if errors:
|
||||
raise Exception(errors)
|
||||
|
|
|
@ -133,3 +133,73 @@ class BookViews(TestCase):
|
|||
|
||||
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
|
||||
self.assertEqual(models.ReadThrough.objects.get().book, edition2)
|
||||
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_move_ratings_on_switch_edition(self, *_):
|
||||
"""updates user's rating on a book to new edition"""
|
||||
work = models.Work.objects.create(title="test work")
|
||||
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
|
||||
edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
|
||||
|
||||
models.ReviewRating.objects.create(
|
||||
book=edition1,
|
||||
user=self.local_user,
|
||||
rating=3,
|
||||
)
|
||||
|
||||
self.assertIsInstance(
|
||||
models.ReviewRating.objects.get(user=self.local_user, book=edition1),
|
||||
models.ReviewRating,
|
||||
)
|
||||
with self.assertRaises(models.ReviewRating.DoesNotExist):
|
||||
models.ReviewRating.objects.get(user=self.local_user, book=edition2)
|
||||
|
||||
request = self.factory.post("", {"edition": edition2.id})
|
||||
request.user = self.local_user
|
||||
views.switch_edition(request)
|
||||
|
||||
self.assertIsInstance(
|
||||
models.ReviewRating.objects.get(user=self.local_user, book=edition2),
|
||||
models.ReviewRating,
|
||||
)
|
||||
with self.assertRaises(models.ReviewRating.DoesNotExist):
|
||||
models.ReviewRating.objects.get(user=self.local_user, book=edition1)
|
||||
|
||||
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
|
||||
@patch("bookwyrm.activitystreams.populate_stream_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_book_statuses_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
def test_move_reviews_on_switch_edition(self, *_):
|
||||
"""updates user's review on a book to new edition"""
|
||||
work = models.Work.objects.create(title="test work")
|
||||
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
|
||||
edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
|
||||
|
||||
models.Review.objects.create(
|
||||
book=edition1,
|
||||
user=self.local_user,
|
||||
name="blah",
|
||||
rating=3,
|
||||
content="not bad",
|
||||
)
|
||||
|
||||
self.assertIsInstance(
|
||||
models.Review.objects.get(user=self.local_user, book=edition1),
|
||||
models.Review,
|
||||
)
|
||||
with self.assertRaises(models.Review.DoesNotExist):
|
||||
models.Review.objects.get(user=self.local_user, book=edition2)
|
||||
|
||||
request = self.factory.post("", {"edition": edition2.id})
|
||||
request.user = self.local_user
|
||||
views.switch_edition(request)
|
||||
|
||||
self.assertIsInstance(
|
||||
models.Review.objects.get(user=self.local_user, book=edition2),
|
||||
models.Review,
|
||||
)
|
||||
with self.assertRaises(models.Review.DoesNotExist):
|
||||
models.Review.objects.get(user=self.local_user, book=edition1)
|
||||
|
|
|
@ -219,3 +219,48 @@ class ShelfViews(TestCase):
|
|||
view(request, request.user.username, shelf.identifier)
|
||||
|
||||
self.assertEqual(shelf.name, "To Read")
|
||||
|
||||
def test_filter_shelf_found(self, *_):
|
||||
"""display books that match a filter keyword"""
|
||||
models.ShelfBook.objects.create(
|
||||
book=self.book,
|
||||
shelf=self.shelf,
|
||||
user=self.local_user,
|
||||
)
|
||||
shelf_book = models.ShelfBook.objects.create(
|
||||
book=self.book,
|
||||
shelf=self.local_user.shelf_set.first(),
|
||||
user=self.local_user,
|
||||
)
|
||||
view = views.Shelf.as_view()
|
||||
request = self.factory.get("", {"filter": shelf_book.book.title})
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.shelf.shelf.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.local_user.username)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(len(result.context_data["books"].object_list), 1)
|
||||
self.assertEqual(
|
||||
result.context_data["books"].object_list[0].title,
|
||||
shelf_book.book.title,
|
||||
)
|
||||
|
||||
def test_filter_shelf_none(self, *_):
|
||||
"""display a message when no books match a filter keyword"""
|
||||
models.ShelfBook.objects.create(
|
||||
book=self.book,
|
||||
shelf=self.shelf,
|
||||
user=self.local_user,
|
||||
)
|
||||
view = views.Shelf.as_view()
|
||||
request = self.factory.get("", {"filter": "NOPE"})
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.shelf.shelf.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.local_user.username)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
self.assertEqual(len(result.context_data["books"].object_list), 0)
|
||||
|
|
|
@ -103,4 +103,13 @@ def switch_edition(request):
|
|||
readthrough.book = new_edition
|
||||
readthrough.save()
|
||||
|
||||
reviews = models.Review.objects.filter(
|
||||
book__parent_work=new_edition.parent_work, user=request.user
|
||||
)
|
||||
for review in reviews.all():
|
||||
# because ratings are a subclass of reviews,
|
||||
# this will pick up both ratings and reviews
|
||||
review.book = new_edition
|
||||
review.save()
|
||||
|
||||
return redirect(f"/book/{new_edition.id}")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" search views"""
|
||||
|
||||
import re
|
||||
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
|
@ -39,6 +40,7 @@ class Search(View):
|
|||
|
||||
endpoints = {
|
||||
"book": book_search,
|
||||
"author": author_search,
|
||||
"user": user_search,
|
||||
"list": list_search,
|
||||
}
|
||||
|
@ -90,6 +92,31 @@ def book_search(request):
|
|||
return TemplateResponse(request, "search/book.html", data)
|
||||
|
||||
|
||||
def author_search(request):
|
||||
"""search for an author"""
|
||||
query = request.GET.get("q")
|
||||
query = query.strip()
|
||||
data = {"type": "author", "query": query}
|
||||
|
||||
results = (
|
||||
models.Author.objects.annotate(
|
||||
similarity=TrigramSimilarity("name", query),
|
||||
)
|
||||
.filter(
|
||||
similarity__gt=0.1,
|
||||
)
|
||||
.order_by("-similarity")
|
||||
)
|
||||
|
||||
paginated = Paginator(results, PAGE_LENGTH)
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
data["results"] = page
|
||||
data["page_range"] = paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
)
|
||||
return TemplateResponse(request, "search/author.html", data)
|
||||
|
||||
|
||||
def user_search(request):
|
||||
"""user search: search for a user"""
|
||||
viewer = request.user
|
||||
|
|
1
bw-dev
1
bw-dev
|
@ -156,6 +156,7 @@ case "$CMD" in
|
|||
git checkout l10n_main locale/fi_FI
|
||||
git checkout l10n_main locale/fr_FR
|
||||
git checkout l10n_main locale/gl_ES
|
||||
git checkout l10n_main locale/ko_KR
|
||||
git checkout l10n_main locale/it_IT
|
||||
git checkout l10n_main locale/lt_LT
|
||||
git checkout l10n_main locale/nl_NL
|
||||
|
|
BIN
locale/ko_KR/LC_MESSAGES/django.mo
Normal file
BIN
locale/ko_KR/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
7177
locale/ko_KR/LC_MESSAGES/django.po
Normal file
7177
locale/ko_KR/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,7 @@ aiohttp==3.9.2
|
|||
bleach==5.0.1
|
||||
celery==5.2.7
|
||||
colorthief==0.2.1
|
||||
Django==3.2.23
|
||||
Django==3.2.24
|
||||
django-celery-beat==2.4.0
|
||||
bw-file-resubmit==0.6.0rc2
|
||||
django-compressor==4.3.1
|
||||
|
|
Loading…
Reference in a new issue