Merge branch 'main' into import-limit
This commit is contained in:
commit
d0adb370cd
45 changed files with 863 additions and 884 deletions
|
@ -92,4 +92,3 @@ class Author(BookData):
|
|||
bio: str = ""
|
||||
wikipediaLink: str = ""
|
||||
type: str = "Author"
|
||||
website: str = ""
|
||||
|
|
|
@ -83,7 +83,7 @@ class Undo(Verb):
|
|||
def action(self):
|
||||
"""find and remove the activity object"""
|
||||
if isinstance(self.object, str):
|
||||
# it may be that sometihng should be done with these, but idk what
|
||||
# it may be that something should be done with these, but idk what
|
||||
# this seems just to be coming from pleroma
|
||||
return
|
||||
|
||||
|
@ -94,7 +94,7 @@ class Undo(Verb):
|
|||
model = apps.get_model("bookwyrm.UserFollows")
|
||||
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
|
||||
# this could be a follow request not a follow proper
|
||||
model = apps.get_model("bookwyrm.UserFollowRequest")
|
||||
obj = self.object.to_model(model=model, save=False, allow_create=False)
|
||||
else:
|
||||
|
|
|
@ -20,7 +20,7 @@ def search(query, min_confidence=0, filters=None, return_first=False):
|
|||
query = query.strip()
|
||||
|
||||
results = None
|
||||
# first, try searching unqiue identifiers
|
||||
# first, try searching unique identifiers
|
||||
# unique identifiers never have spaces, title/author usually do
|
||||
if not " " in query:
|
||||
results = search_identifiers(query, *filters, return_first=return_first)
|
||||
|
|
|
@ -15,7 +15,6 @@ class AuthorForm(CustomForm):
|
|||
"aliases",
|
||||
"bio",
|
||||
"wikipedia_link",
|
||||
"website",
|
||||
"born",
|
||||
"died",
|
||||
"openlibrary_key",
|
||||
|
@ -32,11 +31,10 @@ class AuthorForm(CustomForm):
|
|||
"wikipedia_link": forms.TextInput(
|
||||
attrs={"aria-describedby": "desc_wikipedia_link"}
|
||||
),
|
||||
"website": forms.TextInput(attrs={"aria-describedby": "desc_website"}),
|
||||
"born": forms.SelectDateWidget(attrs={"aria-describedby": "desc_born"}),
|
||||
"died": forms.SelectDateWidget(attrs={"aria-describedby": "desc_died"}),
|
||||
"oepnlibrary_key": forms.TextInput(
|
||||
attrs={"aria-describedby": "desc_oepnlibrary_key"}
|
||||
"openlibrary_key": forms.TextInput(
|
||||
attrs={"aria-describedby": "desc_openlibrary_key"}
|
||||
),
|
||||
"inventaire_id": forms.TextInput(
|
||||
attrs={"aria-describedby": "desc_inventaire_id"}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
# Generated by Django 3.2.16 on 2022-12-28 14:36
|
||||
|
||||
import bookwyrm.models.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0172_alter_user_preferred_language"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="author",
|
||||
name="website",
|
||||
field=bookwyrm.models.fields.CharField(
|
||||
blank=True, max_length=255, null=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="author",
|
||||
name="isfdb",
|
||||
field=bookwyrm.models.fields.CharField(blank=True, max_length=6, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="author",
|
||||
name="isni",
|
||||
field=bookwyrm.models.fields.CharField(
|
||||
blank=True, max_length=19, null=True
|
||||
),
|
||||
),
|
||||
]
|
|
@ -17,15 +17,12 @@ class Author(BookDataModel):
|
|||
max_length=255, blank=True, null=True, deduplication_field=True
|
||||
)
|
||||
isni = fields.CharField(
|
||||
max_length=19, blank=True, null=True, deduplication_field=True
|
||||
max_length=255, blank=True, null=True, deduplication_field=True
|
||||
)
|
||||
gutenberg_id = fields.CharField(
|
||||
max_length=255, blank=True, null=True, deduplication_field=True
|
||||
)
|
||||
isfdb = fields.CharField(
|
||||
max_length=6, blank=True, null=True, deduplication_field=True
|
||||
)
|
||||
website = fields.CharField(
|
||||
max_length=255, blank=True, null=True, deduplication_field=True
|
||||
)
|
||||
# idk probably other keys would be useful here?
|
||||
|
|
|
@ -58,7 +58,7 @@ class BookDataModel(ObjectMixin, BookWyrmModel):
|
|||
max_length=255, blank=True, null=True, deduplication_field=True
|
||||
)
|
||||
isfdb = fields.CharField(
|
||||
max_length=6, blank=True, null=True, deduplication_field=True
|
||||
max_length=255, blank=True, null=True, deduplication_field=True
|
||||
)
|
||||
search_vector = SearchVectorField(null=True)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<meta itemprop="name" content="{{ author.name }}">
|
||||
|
||||
{% firstof author.aliases author.born author.died as details %}
|
||||
{% firstof author.wikipedia_link author.website author.openlibrary_key author.inventaire_id author.isni author.isfdb as links %}
|
||||
{% firstof author.wikipedia_link author.openlibrary_key author.inventaire_id author.isni author.isfdb as links %}
|
||||
{% if details or links %}
|
||||
<div class="column is-3">
|
||||
{% if details %}
|
||||
|
@ -73,14 +73,6 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if author.website %}
|
||||
<div>
|
||||
<a itemprop="sameAs" href="{{ author.website }}" rel="nofollow noopener noreferrer" target="_blank">
|
||||
{% trans "Website" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if author.isni %}
|
||||
<div class="mt-1">
|
||||
<a itemprop="sameAs" href="{{ author.isni_link }}" rel="nofollow noopener noreferrer" target="_blank">
|
||||
|
|
|
@ -57,10 +57,6 @@
|
|||
|
||||
{% include 'snippets/form_errors.html' with errors_list=form.wikipedia_link.errors id="desc_wikipedia_link" %}
|
||||
|
||||
<p class="field"><label class="label" for="id_website">{% trans "Website:" %}</label> {{ form.website }}</p>
|
||||
|
||||
{% include 'snippets/form_errors.html' with errors_list=form.website.errors id="desc_website" %}
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="id_born">{% trans "Birth date:" %}</label>
|
||||
<input type="date" name="born" value="{{ form.born.value|date:'Y-m-d' }}" class="input" id="id_born">
|
||||
|
@ -81,7 +77,7 @@
|
|||
<label class="label" for="id_openlibrary_key">{% trans "Openlibrary key:" %}</label>
|
||||
{{ form.openlibrary_key }}
|
||||
|
||||
{% include 'snippets/form_errors.html' with errors_list=form.oepnlibrary_key.errors id="desc_oepnlibrary_key" %}
|
||||
{% include 'snippets/form_errors.html' with errors_list=form.openlibrary_key.errors id="desc_openlibrary_key" %}
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
{% block title %}{{ book|book_title }}{% endblock %}
|
||||
|
||||
{% block opengraph_images %}
|
||||
{% include 'snippets/opengraph_images.html' with image=book.preview_image %}
|
||||
{% block opengraph %}
|
||||
{% include 'snippets/opengraph.html' with image=book.preview_image %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
<a href="{{ user_path}}">{{ username }}</a> wants to read <a href="{{ book_path }}">{{ book_title }}</a>
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% if finished reading or status.content == '<p>finished reading</p>' %}
|
||||
{% if status.content == 'finished reading' or status.content == '<p>finished reading</p>' %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ user_path}}">{{ username }}</a> finished reading <a href="{{ book_path }}">{{ book_title }}</a>
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% if started reading or status.content == '<p>started reading</p>' %}
|
||||
{% if status.content == 'started reading' or status.content == '<p>started reading</p>' %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ user_path}}">{{ username }}</a> started reading <a href="{{ book_path }}">{{ book_title }}</a>
|
||||
{% endblocktrans %}
|
||||
|
|
|
@ -2,15 +2,13 @@
|
|||
{% load feed_page_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block opengraph_images %}
|
||||
|
||||
{% firstof status.book status.mention_books.first as book %}
|
||||
{% if book %}
|
||||
{% include 'snippets/opengraph_images.html' with image=preview %}
|
||||
{% else %}
|
||||
{% include 'snippets/opengraph_images.html' %}
|
||||
{% endif %}
|
||||
|
||||
{% block opengraph %}
|
||||
{% firstof status.book status.mention_books.first as book %}
|
||||
{% if book %}
|
||||
{% include 'snippets/opengraph.html' with image=preview %}
|
||||
{% else %}
|
||||
{% include 'snippets/opengraph.html' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
@ -44,4 +42,3 @@
|
|||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -15,20 +15,9 @@
|
|||
<link rel="shortcut icon" type="image/x-icon" href="{% if site.favicon %}{% get_media_prefix %}{{ site.favicon }}{% else %}{% static "images/favicon.ico" %}{% endif %}">
|
||||
<link rel="apple-touch-icon" href="{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}">
|
||||
|
||||
{% if preview_images_enabled is True %}
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
{% else %}
|
||||
<meta name="twitter:card" content="summary">
|
||||
{% endif %}
|
||||
<meta name="twitter:title" content="{% if title %}{{ title }} - {% endif %}{{ site.name }}">
|
||||
<meta name="og:title" content="{% if title %}{{ title }} - {% endif %}{{ site.name }}">
|
||||
<meta name="twitter:description" content="{{ site.instance_tagline }}">
|
||||
<meta name="og:description" content="{{ site.instance_tagline }}">
|
||||
|
||||
{% block opengraph_images %}
|
||||
{% include 'snippets/opengraph_images.html' %}
|
||||
{% block opengraph %}
|
||||
{% include 'snippets/opengraph.html' %}
|
||||
{% endblock %}
|
||||
<meta name="twitter:image:alt" content="BookWyrm Logo">
|
||||
|
||||
{% block head_links %}{% endblock %}
|
||||
</head>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<td>
|
||||
<a href="{% url 'settings-users' %}?email=@{{ domain.domain }}">
|
||||
{% with user_count=domain.users.count %}
|
||||
{% blocktrans trimmed count conter=user_count with display_count=user_count|intcomma %}
|
||||
{% blocktrans trimmed count counter=user_count with display_count=user_count|intcomma %}
|
||||
{{ display_count }} user
|
||||
{% plural %}
|
||||
{{ display_count }} users
|
||||
|
@ -62,4 +62,3 @@
|
|||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</header>
|
||||
<div class="column is-narrow">
|
||||
<button type="button" class="button is-small" data-modal-open="{{ domain_modal }}">
|
||||
<span class="icon icon-pencil m-0-mobile" aria-hidden="treu"></span>
|
||||
<span class="icon icon-pencil m-0-mobile" aria-hidden="true"></span>
|
||||
<span class="is-sr-only-mobile">{% trans "Set display name" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -103,4 +103,3 @@
|
|||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
<form method="POST" action="{% url 'settings-themes-delete' theme.id %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="button is-danger is-light is-small">
|
||||
<span class="icon icon-x" aria-hideen="true"></span>
|
||||
<span class="icon icon-x" aria-hidden="true"></span>
|
||||
<span>{% trans "Remove theme" %}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
{% include 'user/books_header.html' with shelf=shelf %}
|
||||
{% endblock %}
|
||||
|
||||
{% block opengraph_images %}
|
||||
{% include 'snippets/opengraph_images.html' with image=user.preview_image %}
|
||||
{% block opengraph %}
|
||||
{% include 'snippets/opengraph.html' with image=user.preview_image %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
book: the Edition object this status is related to. Required unless the status is a reply
|
||||
draft: the content of an existing Status object to be edited (used in delete and redraft)
|
||||
uuid: a unique identifier used to make html "id" attributes unique and clarify javascript controls
|
||||
type: used for uniquely identifying the html elements when mutliple types of posts are available for a book, and usually the endpoint name that the form posts to
|
||||
type: used for uniquely identifying the html elements when multiple types of posts are available for a book, and usually the endpoint name that the form posts to
|
||||
reply_parent: the Status object this post will be in reply to, if applicable
|
||||
{% endcomment %}
|
||||
|
||||
{% block form_open %}
|
||||
{# default form tag syntax, can be overriddden #}
|
||||
{# default form tag syntax, can be overridden #}
|
||||
<form
|
||||
class="is-flex-grow-1{% if not no_script %} submit-status{% endif %}"
|
||||
name="{{ type }}"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% load static %}
|
||||
|
||||
{% if preview_images_enabled is True %}
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
{% if image %}
|
||||
<meta name="twitter:image" content="{{ media_full_url }}{{ image }}">
|
||||
<meta name="og:image" content="{{ media_full_url }}{{ image }}">
|
||||
|
@ -9,6 +10,15 @@
|
|||
<meta name="og:image" content="{{ media_full_url }}{{ site.preview_image }}">
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:image" content="{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}">
|
||||
<meta name="og:image" content="{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}">
|
||||
{% endif %}
|
||||
|
||||
<meta name="twitter:image:alt" content="BookWyrm Logo">
|
||||
|
||||
<meta name="twitter:title" content="{% if title %}{{ title }} - {% endif %}{{ site.name }}">
|
||||
<meta name="og:title" content="{% if title %}{{ title }} - {% endif %}{{ site.name }}">
|
||||
|
||||
<meta name="twitter:description" content="{% if description %}{{ description }}{% else %}{{ site.instance_tagline }}{% endif %}">
|
||||
<meta name="og:description" content="{% if description %}{{ description }}{% else %}{{ site.instance_tagline }}{% endif %}">
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
{% block title %}{{ user.display_name }}{% endblock %}
|
||||
|
||||
{% block opengraph_images %}
|
||||
{% include 'snippets/opengraph_images.html' with image=user.preview_image %}
|
||||
{% block opengraph %}
|
||||
{% include 'snippets/opengraph.html' with image=user.preview_image %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -82,6 +82,7 @@ class EditBookViews(TestCase):
|
|||
form = forms.EditionForm(instance=self.book)
|
||||
form.data["title"] = ""
|
||||
form.data["last_edited_by"] = self.local_user.id
|
||||
form.data["cover-url"] = "http://local.host/cover.jpg"
|
||||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
|
@ -91,6 +92,10 @@ class EditBookViews(TestCase):
|
|||
# Title is unchanged
|
||||
self.book.refresh_from_db()
|
||||
self.assertEqual(self.book.title, "Example Edition")
|
||||
# transient field values are set correctly
|
||||
self.assertEqual(
|
||||
result.context_data["cover_url"], "http://local.host/cover.jpg"
|
||||
)
|
||||
|
||||
def test_edit_book_add_author(self):
|
||||
"""lets a user edit a book with new authors"""
|
||||
|
@ -280,9 +285,14 @@ class EditBookViews(TestCase):
|
|||
form = forms.EditionForm(instance=self.book)
|
||||
form.data["title"] = ""
|
||||
form.data["last_edited_by"] = self.local_user.id
|
||||
form.data["cover-url"] = "http://local.host/cover.jpg"
|
||||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
result = view(request)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
# transient field values are set correctly
|
||||
self.assertEqual(
|
||||
result.context_data["cover_url"], "http://local.host/cover.jpg"
|
||||
)
|
||||
|
|
|
@ -43,6 +43,7 @@ class EditBook(View):
|
|||
form = forms.EditionForm(request.POST, request.FILES, instance=book)
|
||||
|
||||
data = {"book": book, "form": form}
|
||||
ensure_transient_values_persist(request, data)
|
||||
if not form.is_valid():
|
||||
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
||||
|
||||
|
@ -101,6 +102,8 @@ class CreateBook(View):
|
|||
"authors": authors,
|
||||
}
|
||||
|
||||
ensure_transient_values_persist(request, data)
|
||||
|
||||
if not form.is_valid():
|
||||
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
||||
|
||||
|
@ -136,6 +139,11 @@ class CreateBook(View):
|
|||
return redirect(f"/book/{book.id}")
|
||||
|
||||
|
||||
def ensure_transient_values_persist(request, data):
|
||||
"""ensure that values of transient form fields persist when re-rendering the form"""
|
||||
data["cover_url"] = request.POST.get("cover-url")
|
||||
|
||||
|
||||
def add_authors(request, data):
|
||||
"""helper for adding authors"""
|
||||
add_author = [author for author in request.POST.getlist("add_author") if author]
|
||||
|
@ -150,7 +158,6 @@ def add_authors(request, data):
|
|||
data["confirm_mode"] = True
|
||||
# this isn't preserved because it isn't part of the form obj
|
||||
data["remove_authors"] = request.POST.getlist("remove_authors")
|
||||
data["cover_url"] = request.POST.get("cover-url")
|
||||
|
||||
for author in add_author:
|
||||
# filter out empty author fields
|
||||
|
|
|
@ -94,7 +94,7 @@ class List(View):
|
|||
return redirect(book_list.local_path)
|
||||
|
||||
|
||||
def get_list_suggestions(book_list, user, query=None):
|
||||
def get_list_suggestions(book_list, user, query=None, num_suggestions=5):
|
||||
"""What books might a user want to add to a list"""
|
||||
if query:
|
||||
# search for books
|
||||
|
@ -103,20 +103,26 @@ def get_list_suggestions(book_list, user, query=None):
|
|||
filters=[~Q(parent_work__editions__in=book_list.books.all())],
|
||||
)
|
||||
# just suggest whatever books are nearby
|
||||
suggestions = user.shelfbook_set.filter(~Q(book__in=book_list.books.all()))
|
||||
suggestions = [s.book for s in suggestions[:5]]
|
||||
if len(suggestions) < 5:
|
||||
suggestions += [
|
||||
suggestions = user.shelfbook_set.filter(
|
||||
~Q(book__in=book_list.books.all())
|
||||
).distinct()[:num_suggestions]
|
||||
suggestions = [s.book for s in suggestions[:num_suggestions]]
|
||||
if len(suggestions) < num_suggestions:
|
||||
others = [
|
||||
s.default_edition
|
||||
for s in models.Work.objects.filter(
|
||||
~Q(editions__in=book_list.books.all()),
|
||||
).order_by("-updated_date")[: 5 - len(suggestions)]
|
||||
)
|
||||
.distinct()
|
||||
.order_by("-updated_date")[:num_suggestions]
|
||||
]
|
||||
# get 'num_suggestions' unique items
|
||||
suggestions = list(set(suggestions + others))[:num_suggestions]
|
||||
return suggestions
|
||||
|
||||
|
||||
def sort_list(request, items):
|
||||
"""helper to handle the surprisngly involved sorting"""
|
||||
"""helper to handle the surprisingly involved sorting"""
|
||||
# sort_by shall be "order" unless a valid alternative is given
|
||||
sort_by = request.GET.get("sort_by", "order")
|
||||
if sort_by not in ("order", "title", "rating"):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue