mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2025-04-15 14:54:06 +00:00
Merge 9d6136ebf7
into 4c764cd543
This commit is contained in:
commit
7456b0f8ce
18 changed files with 314 additions and 29 deletions
|
@ -39,6 +39,7 @@ class BookList(OrderedCollectionPrivate):
|
|||
|
||||
summary: str = None
|
||||
curation: str = "closed"
|
||||
book: str = None
|
||||
type: str = "BookList"
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,12 @@ class ListForm(CustomForm):
|
|||
fields = ["user", "name", "description", "curation", "privacy", "group"]
|
||||
|
||||
|
||||
class SuggestionListForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.List
|
||||
fields = ["user", "suggests_for"]
|
||||
|
||||
|
||||
class ListItemForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.ListItem
|
||||
|
|
26
bookwyrm/migrations/0180_list_suggests_for.py
Normal file
26
bookwyrm/migrations/0180_list_suggests_for.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.2.20 on 2023-08-01 13:12
|
||||
|
||||
import bookwyrm.models.fields
|
||||
from django.db import migrations
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0179_populate_sort_title"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="list",
|
||||
name="suggests_for",
|
||||
field=bookwyrm.models.fields.OneToOneField(
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="suggestion_list",
|
||||
to="bookwyrm.edition",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -303,7 +303,7 @@ class ForeignKey(ActivitypubRelatedFieldMixin, models.ForeignKey):
|
|||
|
||||
|
||||
class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField):
|
||||
"""activitypub-aware foreign key field"""
|
||||
"""activitypub-aware one to one field"""
|
||||
|
||||
def field_to_activity(self, value):
|
||||
if not value:
|
||||
|
@ -625,11 +625,11 @@ class BooleanField(ActivitypubFieldMixin, models.BooleanField):
|
|||
|
||||
|
||||
class IntegerField(ActivitypubFieldMixin, models.IntegerField):
|
||||
"""activitypub-aware boolean field"""
|
||||
"""activitypub-aware integer field"""
|
||||
|
||||
|
||||
class DecimalField(ActivitypubFieldMixin, models.DecimalField):
|
||||
"""activitypub-aware boolean field"""
|
||||
"""activitypub-aware decimal field"""
|
||||
|
||||
def field_to_activity(self, value):
|
||||
if not value:
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.core.exceptions import PermissionDenied
|
|||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.settings import BASE_URL
|
||||
|
@ -49,6 +50,14 @@ class List(OrderedCollectionMixin, BookWyrmModel):
|
|||
)
|
||||
embed_key = models.UUIDField(unique=True, null=True, editable=False)
|
||||
activity_serializer = activitypub.BookList
|
||||
suggests_for = fields.OneToOneField(
|
||||
"Edition",
|
||||
on_delete=models.PROTECT,
|
||||
activitypub_field="book",
|
||||
related_name="suggestion_list",
|
||||
default=None,
|
||||
null=True,
|
||||
)
|
||||
|
||||
def get_remote_id(self):
|
||||
"""don't want the user to be in there in this case"""
|
||||
|
@ -59,6 +68,27 @@ class List(OrderedCollectionMixin, BookWyrmModel):
|
|||
"""list of books for this shelf, overrides OrderedCollectionMixin"""
|
||||
return self.books.filter(listitem__approved=True).order_by("listitem")
|
||||
|
||||
@property
|
||||
def get_name(self):
|
||||
"""The name comes from the book title if it's a suggestion list"""
|
||||
if self.suggests_for:
|
||||
return _("Suggestions for %(title)s") % {"title": self.suggests_for.title}
|
||||
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def get_description(self):
|
||||
"""The description comes from the book title if it's a suggestion list"""
|
||||
if self.suggests_for:
|
||||
return _(
|
||||
"This is the list of suggestions for <a href='%(url)s'>%(title)s</a>"
|
||||
) % {
|
||||
"title": self.suggests_for.title,
|
||||
"url": self.suggests_for.local_path,
|
||||
}
|
||||
|
||||
return self.description
|
||||
|
||||
class Meta:
|
||||
"""default sorting"""
|
||||
|
||||
|
|
|
@ -383,7 +383,7 @@
|
|||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if lists.exists or request.user.list_set.exists %}
|
||||
{% if lists.exists or list_options.exists %}
|
||||
<section class="content block is-clipped">
|
||||
<h2 class="title is-5">{% trans "Lists" %}</h2>
|
||||
<ul>
|
||||
|
@ -420,8 +420,11 @@
|
|||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<section class="block">
|
||||
{% include "book/suggestion_list/list.html" %}
|
||||
</section>
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
66
bookwyrm/templates/book/suggestion_list/list.html
Normal file
66
bookwyrm/templates/book/suggestion_list/list.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
{% load i18n %}
|
||||
|
||||
<h2 class="title is-3" id="suggestions-section">
|
||||
{% trans "Suggestions" %}
|
||||
</h2>
|
||||
|
||||
{% if book.suggestion_list %}
|
||||
{% with book.suggestion_list.listitem_set.all as items %}
|
||||
|
||||
{% if items|length == 0 %}
|
||||
<section class="section has-background-light is-flex is-flex-direction-column is-align-items-center">
|
||||
<div class="column is-one-third is-centered">
|
||||
{% include "book/suggestion_list/search.html" %}
|
||||
</div>
|
||||
</section>
|
||||
{% else %}
|
||||
<ol class="columns">
|
||||
{% for item in items %}
|
||||
<li class="mb-5 column is-one-quarter">
|
||||
<div class="card">
|
||||
<div class="card-content pb-3">
|
||||
{% with book=item.book %}
|
||||
<div class="is-cover">
|
||||
<a href="{{ item.book.local_path }}" aria-hidden="true">
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-h-m-mobile is-h-m-tablet is-align-items-flex-start' size='medium' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="mt-3">
|
||||
{% include 'snippets/book_titleby.html' %}
|
||||
</p>
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="card-footer is-stacked-mobile has-background-tertiary is-align-items-stretch">
|
||||
<div class="card-footer-item">
|
||||
<p>
|
||||
{% blocktrans trimmed with username=item.user.display_name user_path=item.user.local_path %}
|
||||
Added by <a href="{{ user_path }}">{{ username }}</a>
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer-item">
|
||||
<p>
|
||||
TODO :: endorsements
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="mb-5 column is-one-quarter">
|
||||
{% include "book/suggestion_list/search.html" %}
|
||||
</li>
|
||||
</ol>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<section class="section is-medium has-background-light">
|
||||
<form name="create-list" method="post" action="{% url 'book-create-suggestion-list' book_id=book.id %}#suggestions-section" class="has-text-centered">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||
<input type="hidden" name="suggests_for" value="{{ book.id }}">
|
||||
<button type="submit" class="button is-medium">{% trans "Create suggestion list" %}</button>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
54
bookwyrm/templates/book/suggestion_list/search.html
Normal file
54
bookwyrm/templates/book/suggestion_list/search.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
{% with book.suggestion_list as list %}
|
||||
<h2 class="title is-5">
|
||||
{% trans "Add suggestions" %}
|
||||
</h2>
|
||||
<form name="search" action="{% url 'book' book_id=book.id %}#suggestions-section" method="GET" class="block">
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<input aria-label="{% trans 'Search for a book' %}" class="input" type="text" name="suggestion_query" placeholder="{% trans 'Search for a book' %}" value="{{ query }}">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button" type="submit">
|
||||
<span class="icon icon-search" title="{% trans 'Search' %}">
|
||||
<span class="is-sr-only">{% trans "search" %}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% if query %}
|
||||
<p class="help"><a href="{% url 'book' book_id=book.id %}#suggestions-section">{% trans "Clear search" %}</a></p>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% if not suggested_books %}
|
||||
{% if query %}
|
||||
<p>{% blocktrans %}No books found matching the query "{{ query }}"{% endblocktrans %}</p>{% else %}
|
||||
<p>{% trans "No books found" %}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if suggested_books|length > 0 %}
|
||||
{% for book in suggested_books %}
|
||||
<div class="columns is-mobile is-gapless">
|
||||
<div class="column ml-3">
|
||||
<p>{% include 'snippets/book_titleby.html' with book=book %}</p>
|
||||
|
||||
{% join "add_item" list.id book.id as modal_id %}
|
||||
<button
|
||||
type="button"
|
||||
class="button is-small is-link"
|
||||
data-modal-open="{{ modal_id }}"
|
||||
>
|
||||
{% trans "Add" %}
|
||||
</button>
|
||||
{% include "lists/add_item_modal.html" with id=modal_id is_suggestion=True %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
|
@ -19,7 +19,11 @@
|
|||
<form
|
||||
name="add-book-{{ book.id }}"
|
||||
method="POST"
|
||||
action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}"
|
||||
{% if is_suggestion %}
|
||||
action="{% url 'book-add-suggestion' book_id=list.suggests_for.id %}{% if query %}?suggestion_query={{ query }}#suggestions-section{% endif %}"
|
||||
{% else %}
|
||||
action="{% url 'list-add-book' %}{% if query %}?q={{ query }}{% endif %}"
|
||||
{% endif %}
|
||||
>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</p>
|
||||
|
||||
<div class="block content">
|
||||
{% include 'snippets/trimmed_text.html' with full=list.description %}
|
||||
{% include 'snippets/trimmed_text.html' with full=list.get_description %}
|
||||
</div>
|
||||
|
||||
<section>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% load i18n %}
|
||||
{% load list_page_tags %}
|
||||
|
||||
{% block title %}{{ list.name }}{% endblock %}
|
||||
{% block title %}{{ list.get_name }}{% endblock %}
|
||||
|
||||
{% block opengraph %}
|
||||
{% include 'snippets/opengraph.html' with title=list|opengraph_title description=list|opengraph_description %}
|
||||
|
@ -11,14 +11,16 @@
|
|||
{% block content %}
|
||||
<header class="columns content is-mobile">
|
||||
<div class="column">
|
||||
<h1 class="title">{{ list.name }} <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span></h1>
|
||||
<h1 class="title">{{ list.get_name }} <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span></h1>
|
||||
{% if list.suggests_for == None %}
|
||||
<p class="subtitle help">
|
||||
{% include 'lists/created_text.html' with list=list %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow is-flex field is-grouped">
|
||||
{% if request.user == list.user %}
|
||||
{% if request.user == list.user and list.suggests_for == None %}
|
||||
<div class="control">
|
||||
{% trans "Edit List" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit_list" focus="edit_list_header" %}
|
||||
|
@ -33,7 +35,7 @@
|
|||
{% block breadcrumbs %}{% endblock %}
|
||||
|
||||
<div class="block content">
|
||||
{% include 'snippets/trimmed_text.html' with full=list.description %}
|
||||
{% include 'snippets/trimmed_text.html' with full=list.get_description %}
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<li><a href="{% url 'lists' %}">{% trans "Lists" %}</a></li>
|
||||
<li class="is-active">
|
||||
<a href="#" aria-current="page">
|
||||
{{ list.name|truncatechars:30 }}
|
||||
{{ list.get_name|truncatechars:30 }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -177,6 +177,7 @@
|
|||
</section>
|
||||
|
||||
<section class="column is-one-quarter">
|
||||
{% if list.suggests_for == None %}
|
||||
<h2 class="title is-5">
|
||||
{% trans "Sort List" %}
|
||||
</h2>
|
||||
|
@ -199,6 +200,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if request.user.is_authenticated and not list.curation == 'closed' or request.user == list.user %}
|
||||
<h2 class="title is-5 mt-6">
|
||||
{% if list.curation == 'open' or request.user == list.user or list.group|is_member:request.user %}
|
||||
|
@ -275,7 +277,7 @@
|
|||
data-copytext
|
||||
data-copytext-label="{% trans 'Copy embed code' %}"
|
||||
data-copytext-success="{% trans 'Copied!' %}"
|
||||
><iframe style="border-width:0;" id="bookwyrm_list_embed" width="400" height="600" title="{% blocktrans trimmed with list_name=list.name site_name=site.name owner=list.user.display_name %}
|
||||
><iframe style="border-width:0;" id="bookwyrm_list_embed" width="400" height="600" title="{% blocktrans trimmed with list_name=list.get_name site_name=site.name owner=list.user.display_name %}
|
||||
{{ list_name }}, a list by {{owner}} on {{ site_name }}
|
||||
{% endblocktrans %}" src="{{ embed_url }}"></iframe></textarea>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<div class="card is-stretchable">
|
||||
<header class="card-header">
|
||||
<h4 class="card-header-title is-clipped">
|
||||
<a href="{{ list.local_path }}" class="is-clipped">{{ list.name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
|
||||
<a href="{{ list.local_path }}" class="is-clipped">{{ list.get_name }}</a> <span class="subtitle">{% include 'snippets/privacy-icons.html' with item=list %}</span>
|
||||
</h4>
|
||||
{% if request.user.is_authenticated and request.user|saved:list %}
|
||||
<div class="card-header-icon">
|
||||
|
@ -33,9 +33,9 @@
|
|||
{% endwith %}
|
||||
|
||||
<div class="card-content is-flex-grow-0">
|
||||
<div class="is-clipped" {% if list.description %}title="{{ list.description }}"{% endif %}>
|
||||
{% if list.description %}
|
||||
{{ list.description|to_markdown|safe|truncatechars_html:30 }}
|
||||
<div class="is-clipped" {% if list.description %}title="{{ list.get_description }}"{% endif %}>
|
||||
{% if list.get_description %}
|
||||
{{ list.get_description|to_markdown|safe|truncatechars_html:30 }}
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
|
|
|
@ -785,6 +785,16 @@ urlpatterns = [
|
|||
views.update_book_from_remote,
|
||||
name="book-update-remote",
|
||||
),
|
||||
re_path(
|
||||
rf"{BOOK_PATH}/create-suggestion-list/?$",
|
||||
views.create_suggestion_list,
|
||||
name="book-create-suggestion-list",
|
||||
),
|
||||
re_path(
|
||||
rf"{BOOK_PATH}/book-add-suggestion/?$",
|
||||
views.book_add_suggestion,
|
||||
name="book-add-suggestion",
|
||||
),
|
||||
re_path(
|
||||
r"^author/(?P<author_id>\d+)/update/(?P<connector_identifier>[\w\.]+)/?$",
|
||||
views.update_author_from_remote,
|
||||
|
|
|
@ -60,6 +60,8 @@ from .books.books import (
|
|||
upload_cover,
|
||||
add_description,
|
||||
resolve_book,
|
||||
create_suggestion_list,
|
||||
book_add_suggestion,
|
||||
)
|
||||
from .books.series import BookSeriesBy
|
||||
from .books.books import update_book_from_remote
|
||||
|
|
|
@ -4,7 +4,8 @@ from uuid import uuid4
|
|||
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Avg, Q
|
||||
from django.db import transaction
|
||||
from django.db.models import Avg, Q, Max
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
|
@ -23,6 +24,7 @@ from bookwyrm.views.helpers import (
|
|||
maybe_redirect_local_path,
|
||||
get_mergeable_object_or_404,
|
||||
)
|
||||
from bookwyrm.views.list.list import get_list_suggestions, increment_order_in_reverse
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
|
@ -86,9 +88,15 @@ class Book(View):
|
|||
queryset = queryset.select_related("user").order_by("-published_date")
|
||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||
|
||||
lists = models.List.privacy_filter(request.user,).filter(
|
||||
listitem__approved=True,
|
||||
listitem__book__in=book.parent_work.editions.all(),
|
||||
lists = (
|
||||
models.List.privacy_filter(
|
||||
request.user,
|
||||
)
|
||||
.filter(
|
||||
listitem__approved=True,
|
||||
listitem__book__in=book.parent_work.editions.all(),
|
||||
)
|
||||
.filter(suggests_for__isnull=True)
|
||||
)
|
||||
data = {
|
||||
"book": book,
|
||||
|
@ -104,10 +112,13 @@ class Book(View):
|
|||
"rating": reviews.aggregate(Avg("rating"))["rating__avg"],
|
||||
"lists": lists,
|
||||
"update_error": kwargs.get("update_error", False),
|
||||
"query": request.GET.get("suggestion_query", ""),
|
||||
}
|
||||
|
||||
if request.user.is_authenticated:
|
||||
data["list_options"] = request.user.list_set.exclude(id__in=data["lists"])
|
||||
data["list_options"] = request.user.list_set.filter(
|
||||
suggests_for__isnull=True
|
||||
).exclude(id__in=data["lists"])
|
||||
data["file_link_form"] = forms.FileLinkForm()
|
||||
readthroughs = models.ReadThrough.objects.filter(
|
||||
user=request.user,
|
||||
|
@ -136,6 +147,13 @@ class Book(View):
|
|||
"comment_count": book.comment_set.filter(**filters).count(),
|
||||
"quotation_count": book.quotation_set.filter(**filters).count(),
|
||||
}
|
||||
if hasattr(book, "suggestion_list"):
|
||||
data["suggested_books"] = get_list_suggestions(
|
||||
book.suggestion_list,
|
||||
request.user,
|
||||
query=request.GET.get("suggestion_query", ""),
|
||||
ignore_book=book,
|
||||
)
|
||||
|
||||
return TemplateResponse(request, "book/book.html", data)
|
||||
|
||||
|
@ -223,3 +241,48 @@ def update_book_from_remote(request, book_id, connector_identifier):
|
|||
return Book().get(request, book_id, update_error=True)
|
||||
|
||||
return redirect("book", book.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def create_suggestion_list(request, book_id):
|
||||
"""create a suggestion_list"""
|
||||
form = forms.SuggestionListForm(request.POST)
|
||||
book = get_object_or_404(models.Edition, id=book_id)
|
||||
|
||||
if not form.is_valid():
|
||||
return redirect("book", book.id)
|
||||
suggestion_list = form.save(request, commit=False)
|
||||
|
||||
# default values for the suggestion list
|
||||
suggestion_list.privacy = "public"
|
||||
suggestion_list.curation = "open"
|
||||
suggestion_list.save()
|
||||
|
||||
return redirect("book", book.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
@transaction.atomic
|
||||
def book_add_suggestion(request, book_id):
|
||||
"""put a book on the suggestion list"""
|
||||
book_list = get_object_or_404(models.List, id=request.POST.get("book_list"))
|
||||
|
||||
form = forms.ListItemForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return Book().get(request, book_id, add_failed=True)
|
||||
|
||||
item = form.save(request, commit=False)
|
||||
|
||||
# add the book at the latest order of approved books, before pending books
|
||||
order_max = (
|
||||
book_list.listitem_set.filter(approved=True).aggregate(Max("order"))[
|
||||
"order__max"
|
||||
]
|
||||
) or 0
|
||||
increment_order_in_reverse(book_list.id, order_max + 1)
|
||||
item.order = order_max + 1
|
||||
item.save()
|
||||
|
||||
return Book().get(request, book_id, add_succeeded=True)
|
||||
|
|
|
@ -79,7 +79,10 @@ class List(View):
|
|||
|
||||
if request.user.is_authenticated:
|
||||
data["suggested_books"] = get_list_suggestions(
|
||||
book_list, request.user, query=query
|
||||
book_list,
|
||||
request.user,
|
||||
query=query,
|
||||
ignore_book=book_list.suggests_for,
|
||||
)
|
||||
return TemplateResponse(request, "lists/list.html", data)
|
||||
|
||||
|
@ -100,24 +103,32 @@ class List(View):
|
|||
return redirect_to_referer(request, book_list.local_path)
|
||||
|
||||
|
||||
def get_list_suggestions(book_list, user, query=None, num_suggestions=5):
|
||||
def get_list_suggestions(
|
||||
book_list, user, query=None, num_suggestions=5, ignore_book=None
|
||||
):
|
||||
"""What books might a user want to add to a list"""
|
||||
if query:
|
||||
# search for books
|
||||
return book_search.search(
|
||||
query,
|
||||
filters=[~Q(parent_work__editions__in=book_list.books.all())],
|
||||
filters=[
|
||||
~Q(parent_work__editions__in=book_list.books.all()),
|
||||
~Q(parent_work__editions__in=[ignore_book]),
|
||||
],
|
||||
)
|
||||
# just suggest whatever books are nearby
|
||||
suggestions = user.shelfbook_set.filter(
|
||||
~Q(book__in=book_list.books.all())
|
||||
).distinct()[:num_suggestions]
|
||||
suggestions = (
|
||||
user.shelfbook_set.filter(~Q(book__in=book_list.books.all()))
|
||||
.exclude(book=ignore_book)
|
||||
.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()),
|
||||
~Q(editions__in=[ignore_book]),
|
||||
)
|
||||
.distinct()
|
||||
.order_by("-updated_date")[:num_suggestions]
|
||||
|
|
|
@ -24,6 +24,7 @@ class Lists(View):
|
|||
lists = ListsStream().get_list_stream(request.user)
|
||||
else:
|
||||
lists = models.List.objects.filter(privacy="public")
|
||||
lists = lists.filter(suggests_for__isnull=True)
|
||||
paginated = Paginator(lists, 12)
|
||||
data = {
|
||||
"lists": paginated.get_page(request.GET.get("page")),
|
||||
|
@ -74,7 +75,11 @@ class UserLists(View):
|
|||
"""display a book list"""
|
||||
|
||||
user = get_user_from_username(request.user, username)
|
||||
lists = models.List.privacy_filter(request.user).filter(user=user)
|
||||
lists = (
|
||||
models.List.privacy_filter(request.user)
|
||||
.filter(user=user)
|
||||
.filter(suggests_for__isnull=True)
|
||||
)
|
||||
paginated = Paginator(lists, 12)
|
||||
|
||||
data = {
|
||||
|
|
Loading…
Reference in a new issue