Merge pull request #2834 from zachflanders/2678
Add support for title sort to ignore initial article
This commit is contained in:
commit
ac4276f212
10 changed files with 96 additions and 5 deletions
|
@ -20,6 +20,7 @@ class EditionForm(CustomForm):
|
||||||
model = models.Edition
|
model = models.Edition
|
||||||
fields = [
|
fields = [
|
||||||
"title",
|
"title",
|
||||||
|
"sort_title",
|
||||||
"subtitle",
|
"subtitle",
|
||||||
"description",
|
"description",
|
||||||
"series",
|
"series",
|
||||||
|
@ -45,6 +46,9 @@ class EditionForm(CustomForm):
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
"title": forms.TextInput(attrs={"aria-describedby": "desc_title"}),
|
"title": forms.TextInput(attrs={"aria-describedby": "desc_title"}),
|
||||||
|
"sort_title": forms.TextInput(
|
||||||
|
attrs={"aria-describedby": "desc_sort_title"}
|
||||||
|
),
|
||||||
"subtitle": forms.TextInput(attrs={"aria-describedby": "desc_subtitle"}),
|
"subtitle": forms.TextInput(attrs={"aria-describedby": "desc_subtitle"}),
|
||||||
"description": forms.Textarea(
|
"description": forms.Textarea(
|
||||||
attrs={"aria-describedby": "desc_description"}
|
attrs={"aria-describedby": "desc_description"}
|
||||||
|
|
|
@ -24,7 +24,7 @@ class SortListForm(forms.Form):
|
||||||
sort_by = ChoiceField(
|
sort_by = ChoiceField(
|
||||||
choices=(
|
choices=(
|
||||||
("order", _("List Order")),
|
("order", _("List Order")),
|
||||||
("title", _("Book Title")),
|
("sort_title", _("Book Title")),
|
||||||
("rating", _("Rating")),
|
("rating", _("Rating")),
|
||||||
),
|
),
|
||||||
label=_("Sort By"),
|
label=_("Sort By"),
|
||||||
|
|
49
bookwyrm/migrations/0179_populate_sort_title.py
Normal file
49
bookwyrm/migrations/0179_populate_sort_title.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import re
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from django.db import migrations, transaction
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from bookwyrm.settings import LANGUAGE_ARTICLES
|
||||||
|
|
||||||
|
|
||||||
|
def set_sort_title(edition):
|
||||||
|
articles = chain(
|
||||||
|
*(LANGUAGE_ARTICLES.get(language, ()) for language in tuple(edition.languages))
|
||||||
|
)
|
||||||
|
edition.sort_title = re.sub(
|
||||||
|
f'^{" |^".join(articles)} ', "", str(edition.title).lower()
|
||||||
|
)
|
||||||
|
return edition
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def populate_sort_title(apps, schema_editor):
|
||||||
|
Edition = apps.get_model("bookwyrm", "Edition")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
editions_wo_sort_title = Edition.objects.using(db_alias).filter(
|
||||||
|
Q(sort_title__isnull=True) | Q(sort_title__exact="")
|
||||||
|
)
|
||||||
|
batch_size = 1000
|
||||||
|
start = 0
|
||||||
|
end = batch_size
|
||||||
|
while True:
|
||||||
|
batch = editions_wo_sort_title[start:end]
|
||||||
|
if not batch.exists():
|
||||||
|
break
|
||||||
|
Edition.objects.bulk_update(
|
||||||
|
(set_sort_title(edition) for edition in batch), ["sort_title"]
|
||||||
|
)
|
||||||
|
start = end
|
||||||
|
end += batch_size
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0178_auto_20230328_2132"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(populate_sort_title),
|
||||||
|
]
|
|
@ -1,4 +1,5 @@
|
||||||
""" database schema for books and shelves """
|
""" database schema for books and shelves """
|
||||||
|
from itertools import chain
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.contrib.postgres.search import SearchVectorField
|
from django.contrib.postgres.search import SearchVectorField
|
||||||
|
@ -17,6 +18,7 @@ from bookwyrm.preview_images import generate_edition_preview_image_task
|
||||||
from bookwyrm.settings import (
|
from bookwyrm.settings import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
DEFAULT_LANGUAGE,
|
DEFAULT_LANGUAGE,
|
||||||
|
LANGUAGE_ARTICLES,
|
||||||
ENABLE_PREVIEW_IMAGES,
|
ENABLE_PREVIEW_IMAGES,
|
||||||
ENABLE_THUMBNAIL_GENERATION,
|
ENABLE_THUMBNAIL_GENERATION,
|
||||||
)
|
)
|
||||||
|
@ -363,6 +365,19 @@ class Edition(Book):
|
||||||
for author_id in self.authors.values_list("id", flat=True):
|
for author_id in self.authors.values_list("id", flat=True):
|
||||||
cache.delete(f"author-books-{author_id}")
|
cache.delete(f"author-books-{author_id}")
|
||||||
|
|
||||||
|
# Create sort title by removing articles from title
|
||||||
|
if self.sort_title in [None, ""]:
|
||||||
|
if self.sort_title in [None, ""]:
|
||||||
|
articles = chain(
|
||||||
|
*(
|
||||||
|
LANGUAGE_ARTICLES.get(language, ())
|
||||||
|
for language in tuple(self.languages)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.sort_title = re.sub(
|
||||||
|
f'^{" |^".join(articles)} ', "", str(self.title).lower()
|
||||||
|
)
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -312,6 +312,9 @@ LANGUAGES = [
|
||||||
("zh-hant", _("繁體中文 (Traditional Chinese)")),
|
("zh-hant", _("繁體中文 (Traditional Chinese)")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
LANGUAGE_ARTICLES = {
|
||||||
|
"English": {"the", "a", "an"},
|
||||||
|
}
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,15 @@
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.title.errors id="desc_title" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.title.errors id="desc_title" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="id_sort_title">
|
||||||
|
{% trans "Sort Title:" %}
|
||||||
|
</label>
|
||||||
|
<input type="text" name="sort_title" value="{{ form.sort_title.value|default:'' }}" maxlength="255" class="input" required="" id="id_sort_title" aria-describedby="desc_sort_title">
|
||||||
|
|
||||||
|
{% include 'snippets/form_errors.html' with errors_list=form.sort_title.errors id="desc_sort_title" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_subtitle">
|
<label class="label" for="id_subtitle">
|
||||||
{% trans "Subtitle:" %}
|
{% trans "Subtitle:" %}
|
||||||
|
|
|
@ -145,7 +145,7 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Cover"%}</th>
|
<th>{% trans "Cover"%}</th>
|
||||||
<th>{% trans "Title" as text %}{% include 'snippets/table-sort-header.html' with field="title" sort=sort text=text %}</th>
|
<th>{% trans "Title" as text %}{% include 'snippets/table-sort-header.html' with field="sort_title" sort=sort text=text %}</th>
|
||||||
<th>{% trans "Author" as text %}{% include 'snippets/table-sort-header.html' with field="author" sort=sort text=text %}</th>
|
<th>{% trans "Author" as text %}{% include 'snippets/table-sort-header.html' with field="author" sort=sort text=text %}</th>
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
{% if is_self %}
|
{% if is_self %}
|
||||||
|
|
|
@ -132,3 +132,14 @@ class Book(TestCase):
|
||||||
self.assertIsNotNone(book.cover_bw_book_xlarge_jpg.url)
|
self.assertIsNotNone(book.cover_bw_book_xlarge_jpg.url)
|
||||||
self.assertIsNotNone(book.cover_bw_book_xxlarge_webp.url)
|
self.assertIsNotNone(book.cover_bw_book_xxlarge_webp.url)
|
||||||
self.assertIsNotNone(book.cover_bw_book_xxlarge_jpg.url)
|
self.assertIsNotNone(book.cover_bw_book_xxlarge_jpg.url)
|
||||||
|
|
||||||
|
def test_populate_sort_title(self):
|
||||||
|
"""The sort title should remove the initial article on save"""
|
||||||
|
books = (
|
||||||
|
models.Edition.objects.create(
|
||||||
|
title=f"{article} Test Edition", languages=[langauge]
|
||||||
|
)
|
||||||
|
for langauge, articles in settings.LANGUAGE_ARTICLES.items()
|
||||||
|
for article in articles
|
||||||
|
)
|
||||||
|
self.assertTrue(all(book.sort_title == "test edition" for book in books))
|
||||||
|
|
|
@ -129,7 +129,7 @@ def sort_list(request, items):
|
||||||
"""helper to handle the surprisingly involved sorting"""
|
"""helper to handle the surprisingly involved sorting"""
|
||||||
# sort_by shall be "order" unless a valid alternative is given
|
# sort_by shall be "order" unless a valid alternative is given
|
||||||
sort_by = request.GET.get("sort_by", "order")
|
sort_by = request.GET.get("sort_by", "order")
|
||||||
if sort_by not in ("order", "title", "rating"):
|
if sort_by not in ("order", "sort_title", "rating"):
|
||||||
sort_by = "order"
|
sort_by = "order"
|
||||||
|
|
||||||
# direction shall be "ascending" unless a valid alternative is given
|
# direction shall be "ascending" unless a valid alternative is given
|
||||||
|
@ -139,7 +139,7 @@ def sort_list(request, items):
|
||||||
|
|
||||||
directional_sort_by = {
|
directional_sort_by = {
|
||||||
"order": "order",
|
"order": "order",
|
||||||
"title": "book__title",
|
"sort_title": "book__sort_title",
|
||||||
"rating": "average_rating",
|
"rating": "average_rating",
|
||||||
}[sort_by]
|
}[sort_by]
|
||||||
if direction == "descending":
|
if direction == "descending":
|
||||||
|
|
|
@ -128,7 +128,7 @@ class Shelf(View):
|
||||||
def sort_books(books, sort):
|
def sort_books(books, sort):
|
||||||
"""Books in shelf sorting"""
|
"""Books in shelf sorting"""
|
||||||
sort_fields = [
|
sort_fields = [
|
||||||
"title",
|
"sort_title",
|
||||||
"author",
|
"author",
|
||||||
"shelved_date",
|
"shelved_date",
|
||||||
"start_date",
|
"start_date",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue