mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2025-02-27 08:16:32 +00:00
Associate suggestions with works instead of editions
This commit is contained in:
parent
299ac0631d
commit
5deb779efd
13 changed files with 47 additions and 58 deletions
|
@ -1,13 +0,0 @@
|
||||||
# Generated by Django 4.2.15 on 2024-08-27 14:53
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("bookwyrm", "0209_suggestionlist_alter_listitem_options_and_more"),
|
|
||||||
("bookwyrm", "0209_user_show_ratings"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = []
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 4.2.15 on 2024-08-27 01:20
|
# Generated by Django 4.2.15 on 2024-08-27 17:27
|
||||||
|
|
||||||
import bookwyrm.models.activitypub_mixin
|
import bookwyrm.models.activitypub_mixin
|
||||||
import bookwyrm.models.fields
|
import bookwyrm.models.fields
|
||||||
|
@ -10,7 +10,7 @@ import django.db.models.deletion
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("bookwyrm", "0208_merge_0207_merge_20240629_0626_0207_sqlparse_update"),
|
("bookwyrm", "0209_user_show_ratings"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -140,7 +140,7 @@ class Migration(migrations.Migration):
|
||||||
field=bookwyrm.models.fields.OneToOneField(
|
field=bookwyrm.models.fields.OneToOneField(
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
related_name="suggestion_list",
|
related_name="suggestion_list",
|
||||||
to="bookwyrm.edition",
|
to="bookwyrm.work",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
|
@ -63,7 +63,7 @@ class SuggestionList(AbstractList):
|
||||||
)
|
)
|
||||||
|
|
||||||
suggests_for = fields.OneToOneField(
|
suggests_for = fields.OneToOneField(
|
||||||
"Edition",
|
"Work",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
activitypub_field="book",
|
activitypub_field="book",
|
||||||
related_name="suggestion_list",
|
related_name="suggestion_list",
|
||||||
|
|
|
@ -423,7 +423,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="block">
|
<section class="block">
|
||||||
{% include "book/suggestion_list/list.html" %}
|
{% include "book/suggestion_list/list.html" with list=suggstion_list %}
|
||||||
</section>
|
</section>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<form class="" action="{% url "suggestion-endorse" book.id item.id %}" method="POST">
|
<form class="" action="{% url "suggestion-endorse" work.id item.id %}" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="tag is-info {% if request.user not in item.endorsement.all and request.user != item.user %}is-light{% endif %}" type="submit" {% if request.user == item.user %}disabled{% endif %}>
|
<button class="tag is-info {% if request.user not in item.endorsement.all and request.user != item.user %}is-light{% endif %}" type="submit" {% if request.user == item.user %}disabled{% endif %}>
|
||||||
<span class="icon icon-star-empty" aria-label="{% trans 'Endorsements' %}"></span>
|
<span class="icon icon-star-empty" aria-label="{% trans 'Endorsements' %}"></span>
|
||||||
|
|
|
@ -3,38 +3,35 @@
|
||||||
|
|
||||||
<h2 class="title is-3" id="suggestions-section">
|
<h2 class="title is-3" id="suggestions-section">
|
||||||
{% trans "Suggestions" %}
|
{% trans "Suggestions" %}
|
||||||
<a class="help has-text-weight-normal" href="{% url 'suggestion-list' book_id=book.id %}">
|
<a class="help has-text-weight-normal" href="{% url 'suggestion-list' book_id=work.id %}">
|
||||||
{% blocktrans trimmed with count=book.suggestion_list.suggestionlistitem_set.count|intcomma %}
|
{% trans "View all suggestions" %}
|
||||||
View all {{ count }} suggestions
|
|
||||||
{% endblocktrans %}
|
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% if book.suggestion_list %}
|
{% if suggestion_list %}
|
||||||
|
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
{% blocktrans trimmed with title=book.title %}
|
{% blocktrans trimmed with title=book.title %}
|
||||||
Readers who liked <em>{{ title }}</em> recommend giving these books a try:
|
Readers who liked <em>{{ title }}</em> recommend giving these books a try:
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if items|length == 0 %}
|
{% if items|length == 0 %}
|
||||||
<section class="section has-background-light is-flex is-flex-direction-column is-align-items-center">
|
<section class="section has-background-light is-flex is-flex-direction-column is-align-items-center">
|
||||||
<div class="column is-full is-centered">
|
<div class="column is-full is-centered">
|
||||||
{% include "book/suggestion_list/search.html" %}
|
{% include "book/suggestion_list/search.html" with list=suggestion_list is_suggestion=True %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% else %}
|
{% else %}
|
||||||
<ol class="columns is-multiline">
|
<ol class="columns is-multiline">
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<li class="mb-5 column is-one-third">
|
<li class="mb-5 column is-one-third">
|
||||||
{% include "book/suggestion_list/book_card.html" %}
|
{% include "book/suggestion_list/book_card.html" with list=suggestion_list %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div class="mb-5 column is-full">
|
<div class="mb-5 column is-full">
|
||||||
{% include "book/suggestion_list/search.html" %}
|
{% include "book/suggestion_list/search.html" with list=suggestion_list is_suggestion=True %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -42,7 +39,7 @@
|
||||||
<form name="create-list" method="post" action="{% url 'suggestion-list' book_id=book.id %}#suggestions-section" class="has-text-centered">
|
<form name="create-list" method="post" action="{% url 'suggestion-list' book_id=book.id %}#suggestions-section" class="has-text-centered">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||||
<input type="hidden" name="suggests_for" value="{{ book.id }}">
|
<input type="hidden" name="suggests_for" value="{{ work.id }}">
|
||||||
<button type="submit" class="button is-medium">{% trans "Create suggestion list" %}</button>
|
<button type="submit" class="button is-medium">{% trans "Create suggestion list" %}</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
{% with book.suggestion_list as list %}
|
|
||||||
<details class="details-panel box" {% if query %}open{% endif %} id="add-suggestions">
|
<details class="details-panel box" {% if query %}open{% endif %} id="add-suggestions">
|
||||||
<summary>
|
<summary>
|
||||||
<span class="title is-5" role="heading" aria-level="3">
|
<span class="title is-5" role="heading" aria-level="3">
|
||||||
|
@ -10,11 +9,9 @@
|
||||||
</span>
|
</span>
|
||||||
<span class="details-close icon icon-x" aria-hidden="true"></span>
|
<span class="details-close icon icon-x" aria-hidden="true"></span>
|
||||||
</summary>
|
</summary>
|
||||||
{% url 'book' book_id=book.id as partial_url %}
|
{% with search_url=request.path|add:"#add-suggestions" %}
|
||||||
{% with search_url=partial_url|add:"#add-suggestions" %}
|
|
||||||
{% include "lists/suggestion_search.html" with is_suggestion=True query_param="suggestion_query" columns=True %}
|
{% include "lists/suggestion_search.html" with is_suggestion=True query_param="suggestion_query" columns=True %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</details>
|
</details>
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% load group_tags %}
|
{% load group_tags %}
|
||||||
|
|
||||||
{% block modal-title %}
|
{% block modal-title %}
|
||||||
{% if list.suggests_for or list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
{% if is_suggestion or list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
||||||
{% blocktrans trimmed with title=book|book_title %}
|
{% blocktrans trimmed with title=book|book_title %}
|
||||||
Add "<em>{{ title }}</em>" to this list
|
Add "<em>{{ title }}</em>" to this list
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
<form
|
<form
|
||||||
name="add-book-{{ book.id }}"
|
name="add-book-{{ book.id }}"
|
||||||
method="POST"
|
method="POST"
|
||||||
{% if list.suggests_for %}
|
{% if is_suggestion %}
|
||||||
action="{% url 'book-add-suggestion' book_id=list.suggests_for.id %}{% if query %}?suggestion_query={{ query }}#suggestions-section{% endif %}"
|
action="{% url 'book-add-suggestion' book_id=list.suggests_for.id %}#suggestions-section"
|
||||||
{% else %}
|
{% else %}
|
||||||
action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}"
|
action="{% url 'list-add-book' %}"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
>
|
>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
<div class="buttons is-right is-flex-grow-1">
|
<div class="buttons is-right is-flex-grow-1">
|
||||||
<button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
|
<button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
|
||||||
<button type="submit" class="button is-link">
|
<button type="submit" class="button is-link">
|
||||||
{% if list.suggests_for or list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
{% if is_suggestion or list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
||||||
{% trans "Add" %}
|
{% trans "Add" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Suggest" %}
|
{% trans "Suggest" %}
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% if list.suggests_for %}
|
{% if list.suggests_for %}
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
{% include "book/suggestion_list/endorsement_button.html" with book=list.suggests_for %}
|
{% include "book/suggestion_list/endorsement_button.html" with work=list.suggests_for %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if list.user == request.user or list.group|is_member:request.user %}
|
{% if list.user == request.user or list.group|is_member:request.user %}
|
||||||
|
@ -172,7 +172,7 @@
|
||||||
{% trans "Suggest Books" %}
|
{% trans "Suggest Books" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
{% include "lists/suggestion_search.html" with query_param="q" search_url=add_book_url %}
|
{% include "lists/suggestion_search.html" with query_param="q" search_url=request.path %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<h2 class="title is-5 mt-6" id="embed-label">
|
<h2 class="title is-5 mt-6" id="embed-label">
|
||||||
|
|
|
@ -48,13 +48,13 @@
|
||||||
class="button is-small is-link"
|
class="button is-small is-link"
|
||||||
data-modal-open="{{ modal_id }}"
|
data-modal-open="{{ modal_id }}"
|
||||||
>
|
>
|
||||||
{% if list.suggests_for or list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
{% if is_suggestion or list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
||||||
{% trans "Add" %}
|
{% trans "Add" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Suggest" %}
|
{% trans "Suggest" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
{% include "lists/add_item_modal.html" with id=modal_id is_suggestion=is_suggestion %}
|
{% include "lists/add_item_modal.html" with id=modal_id is_suggestion=is_suggestion list=list %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -90,6 +90,7 @@ class Book(View):
|
||||||
)
|
)
|
||||||
data = {
|
data = {
|
||||||
"book": book,
|
"book": book,
|
||||||
|
"work": book.parent_work,
|
||||||
"statuses": paginated.get_page(request.GET.get("page")),
|
"statuses": paginated.get_page(request.GET.get("page")),
|
||||||
"review_count": reviews.count(),
|
"review_count": reviews.count(),
|
||||||
"ratings": (
|
"ratings": (
|
||||||
|
@ -135,21 +136,22 @@ class Book(View):
|
||||||
"comment_count": book.comment_set.filter(**filters).count(),
|
"comment_count": book.comment_set.filter(**filters).count(),
|
||||||
"quotation_count": book.quotation_set.filter(**filters).count(),
|
"quotation_count": book.quotation_set.filter(**filters).count(),
|
||||||
}
|
}
|
||||||
if hasattr(book, "suggestion_list"):
|
if hasattr(book.parent_work, "suggestion_list"):
|
||||||
|
suggestion_list = book.parent_work.suggestion_list
|
||||||
|
data["suggestion_list"] = suggestion_list
|
||||||
data["items"] = (
|
data["items"] = (
|
||||||
book.suggestion_list.suggestionlistitem_set.prefetch_related(
|
suggestion_list.suggestionlistitem_set.prefetch_related(
|
||||||
"user", "book", "book__authors"
|
"user", "book", "book__authors", "endorsement"
|
||||||
)
|
)
|
||||||
.annotate(endorsement_count=Count("endorsement"))
|
.annotate(endorsement_count=Count("endorsement"))
|
||||||
.order_by("-endorsement_count")[:3]
|
.order_by("-endorsement_count")[:3]
|
||||||
)
|
)
|
||||||
|
|
||||||
data["suggested_books"] = get_list_suggestions(
|
data["suggested_books"] = get_list_suggestions(
|
||||||
book.suggestion_list,
|
suggestion_list,
|
||||||
request.user,
|
request.user,
|
||||||
query=request.GET.get("suggestion_query", ""),
|
query=request.GET.get("suggestion_query", ""),
|
||||||
ignore_book=book,
|
ignore_book=book.parent_work,
|
||||||
)
|
)
|
||||||
|
|
||||||
return TemplateResponse(request, "book/book.html", data)
|
return TemplateResponse(request, "book/book.html", data)
|
||||||
|
|
|
@ -112,13 +112,13 @@ def get_list_suggestions(
|
||||||
query,
|
query,
|
||||||
filters=[
|
filters=[
|
||||||
~Q(parent_work__editions__in=book_list.books.all()),
|
~Q(parent_work__editions__in=book_list.books.all()),
|
||||||
~Q(parent_work__editions__in=[ignore_book]),
|
~Q(parent_work=ignore_book),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
# just suggest whatever books are nearby
|
# just suggest whatever books are nearby
|
||||||
suggestions = (
|
suggestions = (
|
||||||
user.shelfbook_set.filter(~Q(book__in=book_list.books.all()))
|
user.shelfbook_set.filter(~Q(book__in=book_list.books.all()))
|
||||||
.exclude(book=ignore_book)
|
.exclude(book__parent_work=ignore_book)
|
||||||
.distinct()[:num_suggestions]
|
.distinct()[:num_suggestions]
|
||||||
)
|
)
|
||||||
suggestions = [s.book for s in suggestions[:num_suggestions]]
|
suggestions = [s.book for s in suggestions[:num_suggestions]]
|
||||||
|
@ -127,7 +127,7 @@ def get_list_suggestions(
|
||||||
s.default_edition
|
s.default_edition
|
||||||
for s in models.Work.objects.filter(
|
for s in models.Work.objects.filter(
|
||||||
~Q(editions__in=book_list.books.all()),
|
~Q(editions__in=book_list.books.all()),
|
||||||
~Q(editions__in=[ignore_book]),
|
~Q(id=ignore_book.id),
|
||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by("-updated_date")[:num_suggestions]
|
.order_by("-updated_date")[:num_suggestions]
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count
|
from django.db.models import Count, Q
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
@ -26,7 +26,13 @@ class SuggestionList(View):
|
||||||
add_failed = kwargs.get("add_failed", False)
|
add_failed = kwargs.get("add_failed", False)
|
||||||
add_succeeded = kwargs.get("add_succeeded", False)
|
add_succeeded = kwargs.get("add_succeeded", False)
|
||||||
|
|
||||||
book_list = get_object_or_404(models.SuggestionList, suggests_for=book_id)
|
work = models.Work.objects.filter(
|
||||||
|
Q(id=book_id) | Q(editions=book_id)
|
||||||
|
).distinct()
|
||||||
|
print(work.count())
|
||||||
|
work = work.first()
|
||||||
|
|
||||||
|
book_list = get_object_or_404(models.SuggestionList, suggests_for=work)
|
||||||
|
|
||||||
if is_api_request(request):
|
if is_api_request(request):
|
||||||
return ActivitypubResponse(book_list.to_activity(**request.GET))
|
return ActivitypubResponse(book_list.to_activity(**request.GET))
|
||||||
|
@ -53,6 +59,7 @@ class SuggestionList(View):
|
||||||
query = request.GET.get("q", "")
|
query = request.GET.get("q", "")
|
||||||
data = {
|
data = {
|
||||||
"list": book_list,
|
"list": book_list,
|
||||||
|
"work": book_list.suggests_for,
|
||||||
"items": page,
|
"items": page,
|
||||||
"page_range": paginated.get_elided_page_range(
|
"page_range": paginated.get_elided_page_range(
|
||||||
page.number, on_each_side=2, on_ends=1
|
page.number, on_each_side=2, on_ends=1
|
||||||
|
@ -72,19 +79,18 @@ class SuggestionList(View):
|
||||||
return TemplateResponse(request, "lists/list.html", data)
|
return TemplateResponse(request, "lists/list.html", data)
|
||||||
|
|
||||||
@method_decorator(login_required, name="dispatch")
|
@method_decorator(login_required, name="dispatch")
|
||||||
def post(self, request, book_id):
|
def post(self, request, book_id): # pylint: disable=unused-argument
|
||||||
"""create a suggestion_list"""
|
"""create a suggestion_list"""
|
||||||
form = forms.SuggestionListForm(request.POST)
|
form = forms.SuggestionListForm(request.POST)
|
||||||
book = get_object_or_404(models.Edition, id=book_id)
|
|
||||||
|
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
return redirect("book", book.id)
|
return redirect_to_referer(request)
|
||||||
# saving in two steps means django uses the model's custom save functionality,
|
# saving in two steps means django uses the model's custom save functionality,
|
||||||
# which adds an embed key and fixes the privacy and curation settings
|
# which adds an embed key and fixes the privacy and curation settings
|
||||||
suggestion_list = form.save(request, commit=False)
|
suggestion_list = form.save(request, commit=False)
|
||||||
suggestion_list.save()
|
suggestion_list.save()
|
||||||
|
|
||||||
return redirect("book", book.id)
|
return redirect_to_referer(request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|
Loading…
Reference in a new issue